package com.sample.command
{
	import com.sample.Constants;
	import com.sample.model.ModelLocator;
	import com.sample.remote.Delegates;
	import com.sample.remote.FlexServiceDelegate;
	import com.sample.remote.MainConnectionManager;
	import com.sample.remote.UserServiceDelegate;
	import com.sample.vo.UserVO;
	import com.trg.flex.Callback;
	import com.trg.flex.Responder;
	import com.trg.flex.remote.CustomAsyncTokenCall;
	import com.trg.flex.remote.FaultCodeHelper;
	import com.trg.flex.remote.RemoteCall;
	
	import mx.controls.Alert;
	import mx.core.Application;
	import mx.messaging.ChannelSet;
	import mx.messaging.config.ServerConfig;
	import mx.rpc.AsyncToken;
	import mx.rpc.Fault;
	import mx.rpc.events.FaultEvent;
	import mx.rpc.events.ResultEvent;
	
	public class SessionCommands
	{
		public static function get i():SessionCommands {
			return new SessionCommands();
		}
		
		private var userDelegate:UserServiceDelegate = Delegates.userServiceDelegate;
		private var flexDelegate:FlexServiceDelegate = Delegates.flexServiceDelegate;
		private var modelLocator:ModelLocator = ModelLocator.getInstance();
		private var channelSet:ChannelSet;
		
		private var callback:Callback;
		private var loginCallback:Callback;
		
		public function SessionCommands():void {
			channelSet = ServerConfig.getChannelSet("userService");
		}		
		
		/**
		 * Log in the user with the given login and password.
		 */
		public function login(login:String, password:String, callback:Callback = null):void {
			this.loginCallback = callback;
			if (modelLocator.currentUser && modelLocator.currentUser.login != login) {
				throw new Error("login called for user other than the current user.");
			} else {
				channelSet.login(login, password).addResponder(new Responder(loginResult, loginFault));
			}
		}
		
		private function loginResult(event:ResultEvent):void {
			refreshState(loginCallback ? new Callback(finishLogin, finishLogin) : null);
		}
		
		private function loginFault(event:FaultEvent):void {
			switch (FaultCodeHelper.faultCodeToOutcome(event.fault.faultCode)) {
				case FaultCodeHelper.OUTCOME_AUTHENTICATION_FAULT:
					modelLocator.sessionState = Constants.SESSION_STATE_LOGGED_OUT;
					if (loginCallback)
						loginCallback.doFailure(new Error("Incorrect User Name and Password."));
					break;
				case FaultCodeHelper.OUTCOME_CONNECTION_FAULT:
					if (loginCallback)
						loginCallback.doFailure(new Error("Unable to establish connection to server."));
					break;
				default:
					if (loginCallback)
						loginCallback.doFailure(new Error("Unexpected error while attempting to login."));
					break;
			}
		}
		
		private function finishLogin(info:*):void {
			if (info is Error) {
				loginCallback.doFailure(info);
			} else if (modelLocator.sessionState != Constants.SESSION_STATE_LOGGED_IN) {
				loginCallback.doFailure(new Error("Login failed."));
			} else {
				loginCallback.doSuccess(info);
			}
		}
		
		
		/**
		 * Update the model with the current session state
		 */
		public function refreshState(callback:Callback = null):void {
			this.callback = callback;
			var call:RemoteCall = flexDelegate.getCurrentUser().addCallback(refreshStateCallback);
			call.description = "Checking Session State";
			call.showStatus = false;
			call.call(RemoteCall.BYPASS);
		}
		
		private function refreshStateCallback(call:RemoteCall):void {
			if (call.faulted) {
				var fault:Fault = call.fault as Fault;
				var outcome:String = FaultCodeHelper.faultCodeToOutcome(fault.faultCode);
				if (outcome == FaultCodeHelper.OUTCOME_AUTHENTICATION_FAULT) {
					modelLocator.sessionState = Constants.SESSION_STATE_LOGGED_OUT;
					channelSet.logout();
				} else if (outcome == FaultCodeHelper.OUTCOME_CONNECTION_FAULT) {
					if (callback)
						callback.doFailure(new Error("Unable to establish connection to server."));
					return;
				} else {
					if (callback)
						callback.doFailure(new Error("Unexpected error while establishing authentication state."));
					return;
				}
			} else {
				var user:UserVO = call.result as UserVO;
				if (modelLocator.currentUser) {
					if (modelLocator.currentUser.login == user.login) {
						modelLocator.sessionState = Constants.SESSION_STATE_LOGGED_IN;
						MainConnectionManager.instance().authenticated = true;
					} else {
						modelLocator.sessionState = Constants.SESSION_STATE_WRONG_USER;
						if (callback)
							callback.doFailure(new Error("The user is logged into the session is different than the user associated with this application."));
						return;
					}
				} else {
					modelLocator.currentUser = user;
					modelLocator.sessionState = Constants.SESSION_STATE_LOGGED_IN;
					MainConnectionManager.instance().authenticated = true;
				}
				sample(Application.application).loadMainModule();
			}
			if (callback)
				callback.doSuccess(modelLocator.currentUser);
		}
		
		/**
		 * 
		 */
		public function logout(callback:Callback = null):void {
			this.callback = callback;
			var call:CustomAsyncTokenCall = new CustomAsyncTokenCall(logoutCall, channelSet);
			call.authenticationRequired = false;
			call.addResultCallback(logoutResult).addFaultCallback(logoutFault);
			call.description = "Logout";
			call.showStatus = false;
			call.call('unordered', 'logout'); //we put it in it's own queue to avoid waiting for other calls
		}
		
		private function logoutCall(channelSet:ChannelSet):AsyncToken {
			return channelSet.logout();
		}
		
		private function logoutResult(result:*):void {
			modelLocator.sessionState = Constants.SESSION_STATE_LOGGED_OUT;
			MainConnectionManager.instance().authenticated = false;
			if (callback)
				callback.doSuccess(result);
		}
		
		private function logoutFault(fault:Fault):void {
			//let's hope this doesn't happen. connection failures should be
			//handled by the RemoteCallManager.
			if (callback)
				callback.doFailure(fault);
		}
		
	}
}